/* weehttpd
* Copyright (c) 2008 Jeffery Myers
*
* This package is free software;  you can redistribute it and/or
* modify it under the terms of the license found in the file
* named COPYING that should have accompanied this file.
*
* THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/

#include "connection.h"
#include "textUtils.h"
#include "timeUtils.h"

std::string printNow ( void )
{
	WeeTime now;
	getUTCTime(now);
	return printTime(now,"UTC");
}

void parseParams(std::map<std::string, std::vector<std::string> > &params, const std::string &text, size_t offset)
{
	std::vector<std::string> items = tokenize(text,"&",0,false,offset);

	for (size_t i = 0; i < items.size(); i++)
	{
		std::string &item = items[i];

		std::string key,val;

		std::vector<std::string> t = tokenize(item,"=",0,false);
		if (t.size() > 1) 
		{
			key = t[0];
			val = t[1];
		}
		else
		{
			key = item;
			val = "";
		}

		if (params.find(key) == params.end())
		{
			std::vector<std::string> tv;
			params[key] = tv;
		}
		params[key].push_back(val);
	}
}

void HTTPConnection::fillRequest(WeeHTTPRequest &req)
{
	req.request = request;
	req.vdir = url_decode(vdir);
	req.resource = url_decode(resource);
	req.headers = header;
	req.cookies.clear();

	req.ip = options.networking->getConnectionIP(connectionID);
	const char *hostmask = options.networking->getConnectionHost(connectionID);
	req.hostmask = hostmask ? hostmask : "";

	// parse the headers here for cookies
	std::map<std::string,std::string>::iterator itr = req.headers.begin();

	while (itr != req.headers.end()) 
	{
		const std::string &key = itr->first;
		if (compare_nocase(key,"cookie") == 0)
		{
			std::vector<std::string> cookie = tokenize(itr->second,"=",2,false);

			if (cookie.size() > 1) 
			{
				req.cookies[cookie[0]] = cookie[1];

				// check for the magic sessionID cookie
				if (compare_nocase(cookie[0],"sessionid") == 0)
					sessionID = atoi(cookie[1].c_str());
			}
		}
		else if (compare_nocase(key,"authorization") == 0)
		{
			std::vector<std::string> auth = tokenize(itr->second," ",2,false);

			if (auth.size() > 1) 
			{
				req.authType = auth[0];
				req.authCredentials = auth[1];

				if (compare_nocase(auth[0],"basic") == 0 && auth[1].size())
				{
					std::string b64 = base64_decode(auth[1]);
					std::vector<std::string> uandp = tokenize(b64,":",2,false);
					if (uandp.size() == 2)
					{
						req.username = uandp[0];
						req.password = uandp[1];
					}
				}
			}
		}

		itr++;
	}

	if (req.request != ePost)
	{
		// parse out the parameters from the resource
		size_t q = req.resource.find_first_of('?');
		if (q != std::string::npos)
		{
			parseParams(req.parameters,req.resource,q+1);
			req.resource.erase(req.resource.begin()+q,req.resource.end());
		}
	}

	if (req.request == ePost && contentSize > 0)
		parseParams(req.parameters,body,0);
	else if (req.request == ePut &&contentSize > 0)
		req.body = body;
}

bool HTTPConnection::update(void)
{
	if (!options.networking->isOpen(connectionID))
		return true;

	// hit the processings
	std::vector<size_t> killList;

	bool closeConnect = false;

	for (size_t i = 0; i < processingTasks.size(); i++)
	{
		if (processingTasks[i].update(connectionID))
		{
			if (processingTasks[i].forceClose)
				closeConnect = true;
			killList.push_back(i);
		}
	}

	std::vector<size_t>::reverse_iterator itr = killList.rbegin();
	while (itr != killList.rend())
	{
		size_t offset = *itr;
		processingTasks.erase(processingTasks.begin()+offset);
		itr++;
	}

	// check the pending to see if they should be restarted
	std::vector<PendingHTTPTask>::iterator pendingItr = pendingTasks.begin();
	while (pendingTasks.size() && pendingItr != pendingTasks.end()) 
	{
		PendingHTTPTask &pendingTask = *pendingItr;

		WeeHTTPVDir *vdir = pendingTask.dir;

		if (!vdir)
		{
			std::vector<PendingHTTPTask>::iterator t = pendingItr;
			t++;
			pendingTasks.erase(pendingItr);
			pendingItr = t;
		} 
		else 
		{
			if (vdir->resumeTask(pendingTask.request.requestID))
			{
				if (vdir->handleRequest(pendingTask.request,pendingTask.reply))
				{
					// if it is done and fire if off
					pendingTask.reply.cookies["SessionID"] = format("%d",pendingTask.request.sessionID);
					pendingTask.generateBody(pendingTask.reply,pendingTask.request.request == eHead);
					processingTasks.push_back(HTTPTask(pendingTask));

					std::vector<PendingHTTPTask>::iterator t = pendingItr;
					t++;
					pendingTasks.erase(pendingItr);
					pendingItr = t;
				} 
				else 
					pendingItr++;
			}
			else 
				pendingItr++;
		}
	}

	return closeConnect;
}

const char* getMimeType(WeeHTTPReply::DocumentType docType)
{
	switch(docType) {
  case WeeHTTPReply::eOctetStream:
	  return "application/octet-stream";

  case WeeHTTPReply::eBinary:
	  return "application/binary";

  case WeeHTTPReply::eHTML:
	  return "text/html";

  case WeeHTTPReply::eCSS:
	  return "text/css";

  case WeeHTTPReply::eXML:
	  return "application/xml";

  case WeeHTTPReply::eJSON:
	  return "application/json";

  default:
	  break;
	}
	return "text/plain";
}

HTTPConnection::HTTPTask::HTTPTask(WeeHTTPReply& r,WeeHTTPDOptions &o, bool noBody):pos(0),options(o)
{
	forceClose = false;
	generateBody(r,noBody);
}

void HTTPConnection::HTTPTask::generateBody (WeeHTTPReply& r, bool noBody)
{
	// start a new one
	pageBuffer = "HTTP/1.1";

	forceClose = true;

	switch(r.returnCode) 
	{
	case WeeHTTPReply::e200OK:
		pageBuffer += " 200 OK\n";
		forceClose = false;
		break;

	case WeeHTTPReply::e301Redirect:
		if (r.redirectLoc.size()) 
		{
			pageBuffer += " 301 Moved Permanently\n";
			pageBuffer += "Location: " + r.redirectLoc + "\n";
		}
		else
			pageBuffer += " 500 Server Error\n";

		pageBuffer += "Server: " + options.serverName + "\n"; //
		pageBuffer += "Version: " + options.serverVersion + "\n"; //
		pageBuffer += "Host: " + options.rootHost + "\n"; //

		break;

	case WeeHTTPReply::e302Found:
		if (r.redirectLoc.size()) 
		{
			pageBuffer += " 302 Found\n";
			pageBuffer += "Location: " + r.redirectLoc + "\n";
		}
		else
			pageBuffer += " 500 Server Error\n";

		pageBuffer += "Server: " + options.serverName + "\n"; //
		pageBuffer += "Version: " + options.serverVersion + "\n"; //
		pageBuffer += "Host: " + options.rootHost + "\n"; //
		break;

	case WeeHTTPReply::e500ServerError:
		pageBuffer += " 500 Server Error\n";
		break;

	case WeeHTTPReply::e401Unauthorized:
		pageBuffer += " 401 Unauthorized\n";
		pageBuffer += "WWW-Authenticate: ";

		if (r.authType.size())
			pageBuffer += r.authType;
		else
			pageBuffer += "Basic";

		pageBuffer += " realm=\"";
		if (r.authRealm.size())
			pageBuffer += r.authRealm;
		else
			pageBuffer += options.rootHost;
		pageBuffer += "\"\n";
		forceClose = false;
		break;

	case WeeHTTPReply::e404NotFound:
		pageBuffer += " 404 Not Found\n";
		forceClose = false;
		break;

	case WeeHTTPReply::e403Forbiden:
		pageBuffer += " 403 Forbidden\n";
		forceClose = false;
		break;
	}

	if(false)
		forceClose = true;

	if (forceClose)
		pageBuffer += "Connection: close\n";

	if (r.getBodySize()) {
		pageBuffer += format("Content-Length: %d\n", r.getBodySize());

		pageBuffer += "Content-Type: ";
		if (r.docType == WeeHTTPReply::eOther && r.otherMimeType.size())
			pageBuffer += r.otherMimeType;
		else
			pageBuffer += getMimeType(r.docType);
		pageBuffer += "\n";
	}

	// write the cache info
	if(r.forceNoCache)
		pageBuffer += "Cache-Control: no-cache\n";

	if (r.md5.size())
		pageBuffer += "Content-MD5: " + r.md5 + "\n";

	// dump the basic stat block
	pageBuffer += "Server: " + options.serverVersion + "\n";

	pageBuffer += "Date: ";
	pageBuffer += printNow();
	pageBuffer += "\n";

	// dump the headers
	std::map<std::string,std::string>::iterator itr = r.headers.begin();

	while (itr != r.headers.end())
	{
		pageBuffer += itr->first + ": " + itr->second + "\n";
		itr++;
	}

	if (r.returnCode == WeeHTTPReply::e200OK)
	{
		itr = r.cookies.begin();
		while (itr != r.cookies.end())
		{
			pageBuffer += "Set-Cookie: " +itr->first + "=" + itr->second + "\n";
			itr++;
		}
	}

	pageBuffer += "\n";

	if (!noBody && r.getBodySize())
	{
		if (r.body.size())
			pageBuffer += r.body;
		else // it's bin data
			pageBuffer.append(r.getBody(),r.getBodySize());
	}
}

HTTPConnection::HTTPTask::HTTPTask(const HTTPTask& t):options(t.options)
{
	pageBuffer = t.pageBuffer;
	pos = t.pos;
	forceClose = t.forceClose;
}

HTTPConnection::HTTPTask & HTTPConnection::HTTPTask::operator =(const HTTPConnection::HTTPTask& t)
{
	options = t.options;
	pageBuffer = t.pageBuffer;
	pos = t.pos;
	forceClose = t.forceClose;
	return *this;
}

bool HTTPConnection::HTTPTask::update(unsigned int connectionID)
{
	// find out how much to write
	if (pos >= pageBuffer.size())
		return true;

	// only send out another message if the buffer is nearing being empty, so we don't flood it out and
	// waste a lot of extra memory.
	if(!options.networking->readyForSend(connectionID))
		return false;

	size_t write = 1000;
	size_t left = pageBuffer.size()-pos;

	if (left <= 1000)
		write = left;

	size_t sent = options.networking->send(connectionID,pageBuffer.getData()+pos,write);
	if (sent == 0)
		return true;

	pos += sent;

	return pos >= pageBuffer.size();
}

// Local Variables: ***
// mode: C++ ***
// tab-width: 8 ***
// c-basic-offset: 2 ***
// indent-tabs-mode: t ***
// End: ***
// ex: shiftwidth=2 tabstop=8
